#ifndef APP_SKYDOME_HPP_
#define APP_SKYDOME_HPP_


#include <device/aerProgram.hpp>
#include <device/aerTexture.hpp>
#include <device/aerRenderTexture.hpp>
#include <misc/aerShape.hpp>
#include <misc/aerScreenQuad.hpp>
#include <aerTimer.hpp>


class SkyDome
{
  private:
    aer::Program m_program;
    aer::Texture m_texture;
    aer::Dome m_mesh;
    
    aer::Matrix4x4 m_scaleMatrix;
    aer::Vector3 m_skyColor;
    float m_cloudSpeed;
    
        
  public:
    SkyDome() {}
    
    ~SkyDome()
    {
      m_program.release();
      m_texture.release();
    }
    
    void init()
    {
      // =============
      // == Program ==
      // =============
      m_program.generate();
        m_program.addShader( aer::Program::VERTEX_SHADER,   "SkyDome.Vertex"   );
        m_program.addShader( aer::Program::FRAGMENT_SHADER, "SkyDome.Fragment" );
      m_program.link();
      
      
      // =============
      // == Texture ==
      // =============
      initTexture();      
      
      // ==========
      // == Mesh ==
      // ==========
      m_mesh.init( 1.0f, 24u);      
      
      // ============
      // == params ==
      // ============
      m_scaleMatrix = glm::scale( glm::mat4(1.0f), glm::vec3(150.0f));
      m_skyColor = aer::Vector3( 0.5f, 0.7f, 1.0f);
      m_cloudSpeed = 0.007f;
      
      
      CHECKGLERROR();
    }
    
    void initTexture()
    {
      const aer::U32 res = 512u;
      
      m_texture.generate();
      m_texture.bind();
        m_texture.setFilters( aer::Texture::LINEAR, aer::Texture::LINEAR);//
        m_texture.setWraps( aer::Texture::REPEAT, aer::Texture::REPEAT);
        m_texture.resize( res, res, GL_RGB);
      m_texture.unbind();
      
      
      // program & RenderTexture to generate the cloud texture
      aer::Program cloudProgram;//
      cloudProgram.generate();
        cloudProgram.addShader( aer::Program::VERTEX_SHADER,   "ScreenMapping.Vertex" );
        cloudProgram.addShader( aer::Program::FRAGMENT_SHADER, "SkyDome.CloudTex.Fragment" );
        cloudProgram.addShader( aer::Program::FRAGMENT_SHADER, "Noise.Include" );
      cloudProgram.link();
      
      aer::RenderTexture cloudRT;
      
      cloudRT.generate();      
      cloudRT.resize( res, res);
      cloudRT.bind();
      cloudRT.setup( &m_texture );
      cloudRT.unbind();
      
      // Generate !
      cloudRT.begin();
      glClear( GL_COLOR_BUFFER_BIT );
      cloudProgram.bind();
      {
        glViewport( 0, 0, res, res);
        cloudProgram.setUniform( "uEnableTiling", true);
        cloudProgram.setUniform( "uTileRes", aer::Vector3(res));
        int seed = 456789*rand()/RAND_MAX;
        cloudProgram.setUniform( "uPermutationSeed", seed);//
        
        aer::ScreenQuad::Draw();
      }
      cloudProgram.unbind();
      cloudRT.end();
      
      cloudProgram.release();
      cloudRT.release();
    }
    
    void resize(aer::U32 width, aer::U32 height) {}
    
    void render(aer::Camera &camera, const aer::Timer& timer)
    {
      // (note : applied outside Skydome)
      //glDisable( GL_DEPTH_TEST );//
      //glDepthMask( GL_FALSE );//
      
      m_program.bind();
      {
        const aer::Matrix4x4 &viewProj = camera.getViewProjectionMatrix();
        // center the dome to the camera (TODO: send the offset as an uniform)
        glm::mat4 translation = glm::translate( glm::mat4(1.0f), camera.getPosition());
        
        aer::Matrix4x4 model = translation * m_scaleMatrix; 
        aer::Matrix4x4 mvp = viewProj * model;          
        m_program.setUniform( "uModelViewProjMatrix", mvp);
        
        float t = timer.getRelativeTime()/1000.0f;
        float timer = m_cloudSpeed * t;
        m_program.setUniform( "uTimer", timer);

        m_program.setUniform( "uCloudTex", 0);
        m_program.setUniform( "uSkyColor", m_skyColor);

        m_texture.bind();
        m_mesh.draw(); 
        m_texture.unbind();
      }
      m_program.unbind(); 
      
      //glDepthMask( GL_TRUE );//
      //glEnable( GL_DEPTH_TEST );//
    }
};

#endif //APP_SKYDOME_HPP_
